package DTask::Link;
use 5.018;
use warnings;

use DTask::Utils qw(wrapMongoDBOp);
use Carp qw(carp);
use Mango::BSON qw(bson_time);
use Mojo::JSON qw(true false);
use Data::Dumper;

my $db;

sub setMango
{
	my $mango = shift;
	$db = $mango->db('dtask');
}

sub linkWith
{
	my ($target_a, $a_id, $target_b, $b_id, $cb) = @_;
	$a_id .= '';
	$b_id .= '';

	if ( $target_a eq $target_b && $a_id eq $b_id )
	{
		$cb->(undef, "link with self");
		return;
	}

	my $delay = Mojo::IOLoop->delay(sub {
		my ($delay, $op_a, $op_a_err, $op_b, $op_b_err ) = @_;
		warn "linkWith: finish link between $target_a $a_id and $target_b $b_id";
		
		my $err = [$op_a_err, $op_b_err];
		$cb->($op_a || $op_b , $err);	
	});

	my $col_a = $db->collection($target_a);
	my $col_b = $db->collection($target_b);

	my $end_a = $delay->begin(0);
	my $end_b = $delay->begin(0);
	_link($col_a, $a_id, $target_b, $b_id, sub {
		$end_a->(@_);	
	});
	_link($col_b, $b_id, $target_a, $a_id, sub {
		$end_b->(@_);	
	});

}

sub _link
{
	my ($col_a, $a_id, $target_b, $b_id, $cb) = @_;

	$col_a->find_one({_id => $a_id },{_id => 1}, sub {
		my ($doc, $err) = wrapMongoDBOp(@_);
		if ($doc->{_id})
		{
			warn "_link: update $a_id link with $target_b $b_id";	
			_update($col_a, $a_id, $target_b, $b_id, sub {
				$cb->(@_);	
			});
		}
		else
		{
			warn "_link: insert  $a_id link with $target_b $b_id";	
			_insert($col_a, $a_id, $target_b, $b_id, sub {
				$cb->(@_);	
			});
		}
		
	});
}

sub _update
{
	my ($col_a, $a_id, $target_b, $b_id, $cb) = @_;
	$col_a->update({_id => $a_id}, { '$addToSet' => {'link.' . $target_b , $b_id }}, sub {
		my ($doc, $err) = wrapMongoDBOp(@_);
#		warn '_update: dump $doc';
#		warn Dumper $doc;
		$cb->( $doc->{nModified} > 0 , $err);	
	});
}

sub _insert
{
	my ($col_a, $a_id, $target_b, $b_id, $cb) = @_;
	$col_a->insert({_id => $a_id, createAt => bson_time, link => {$target_b => [ $b_id ]} }, sub {
		my ($doc, $err) = wrapMongoDBOp(@_);
#		warn '_insert: dump $doc';
#		warn Dumper $doc;
		$cb->(defined $doc , $err);	
	});
}

sub unlinkWith
{
	my ($target_a, $a_id, $target_b, $b_id, $cb) = @_;
	$a_id .= '';
	$b_id .= '';
	my $col_a = $db->collection($target_a);
	my $col_b = $db->collection($target_b);

	my $delay = Mojo::IOLoop->delay(sub {
		my ($delay, $op_a, $op_a_err, $op_b, $op_b_err ) = @_;
		warn "unlinkWith: finish unlink between $target_a $a_id and $target_b $b_id";
		
		my $err = [$op_a_err, $op_b_err];
		$cb->($op_a || $op_b, $err);	
	});

	my $end_a = $delay->begin(0);
	my $end_b = $delay->begin(0);
	_unlink($col_a, $a_id, $target_b, $b_id, sub {
		$end_a->(@_);	
	});
	_unlink($col_b, $b_id, $target_a, $a_id, sub {
		$end_b->(@_);	
	});
}

sub _unlink
{
	my ($col_a, $a_id, $target_b, $b_id, $cb) = @_;
	$col_a->update({_id => $a_id }, { '$pull' => {'link.'. $target_b => $b_id } }, sub {
		my ($doc, $err) = wrapMongoDBOp(@_);
		warn "_unlink done";
		$cb->($doc->{nModified} > 0  , $err );
	});
}

sub setAttr
{
	my ($target, $id, $attr, $cb) = @_;
	$id .= '';
	my $col = $db->collection($target);

	$col->update({_id => $id }, { '$set' => $attr }, sub {
		my ($doc, $err) = wrapMongoDBOp(@_);
		warn Dumper $doc;
		if ($doc->{n} > 0 )
		{
			$cb->($doc->{nModified} > 0 ,  $err);
		}
		else
		{
			$col->insert({_id => $id , createAt => bson_time , %$attr }, sub {
				my ($_id, $err) = wrapMongoDBOp(@_);
				$cb->(defined $_id, $err);
			});
		}
	});
}

sub getAttr
{
	my ($target, $id, $attr, $cb) = @_;
	$id .= '';
	my $col = $db->collection($target);

	$col->find_one({_id => $id}, {_id => 0, $attr => 1} => sub {
		my ($doc, $err)= wrapMongoDBOp(@_);
		$cb->($doc->{$attr}, $err);
	});
}

# DTask::Link::getLinks('bugzilla', "123",'tower_todo' , sub {})
sub getLinks
{
	my ($target_a, $a_id, $target_b, $cb) = @_;
	getAttr($target_a, $a_id,'link', sub {
		my ($links, $err)	= @_;
		if ($links)
		{
			$links = $links->{$target_b} if defined $target_b;
			$cb->($links , $err);
		}
		else
		{
			warn "$target_a $a_id do not have any links";
			$cb->(undef, $err);
		}

	});

}

# DTask::Link::getAllLinks('bugzilla', "123", sub {})
sub getAllLinks
{
	my ($target_a, $a_id, $cb) = @_;
	getLinks($target_a, $a_id, undef, $cb);
}

sub _update_all_link
{
	my ($col,$id, $links, $cb) = @_;
	my $links_added = {};
	#fill $links_added
	for my $target ( keys %$links )
	{
		my $target_ids = $links->{ $target };
		if (@$target_ids)
		{
			$links_added->{ 'link.'. $target } = { '$each' => $target_ids };
		}
	}

	warn "_update_all_link dump \$links_added";
	warn Dumper $links_added;

	if ( keys %$links_added )
	{
		$col->update({_id => $id }, {'$addToSet' => $links_added }, sub {
			my ($doc, $err) = wrapMongoDBOp(@_);	
			$cb->($doc->{nModified} > 0 , $err);
		});
	}
	else
	{
		$cb->(false);
	}
}

sub _insert_all_link
{
	my ($col,$id, $links, $cb) = @_;
	
	$col->insert({_id => $id, createAt => bson_time , link => $links }, sub {
		my ($_id, $err) = wrapMongoDBOp(@_);
		$cb->(defined $_id , $err);
	});
}

sub allLink
{
	my ($target_a , $a_id, $links, $cb) = @_;
	$a_id .= '';
	for my $target ( keys %$links )
	{
		my @target_ids = map { $_ .'' } @{ $links->{ $target } };
		$links->{ $target } = \@target_ids;
	}
	warn 'allLink dump $links';
	warn Dumper $links;

	my $delay = Mojo::IOLoop->delay(
		sub {
			warn "step 1";
			my ($delay) = @_;
			my $col_a = $db->collection($target_a);

			my $end = $delay->begin(0);
			$col_a->find_one({_id => $a_id },{_id => 1}, sub {
				my ($doc, $err) = wrapMongoDBOp(@_);
				if ($doc)
				{
					_update_all_link($col_a, $a_id, $links, sub {
						warn "_update_all_link done";
						$end->(@_);	
					});
				}
				else
				{
					_insert_all_link($col_a, $a_id, $links, sub {
						$end->(@_);	
					});
				}
			});
		
		},
		sub {
			warn "step 2";
			my ($delay, $op, $err) = @_;
			if ($err)
			{
				$cb->($op, $err);
				warn "on error return";
				return;
			}
			else
			{
				for my $target_b ( keys %$links )
				{
					my $col_b = $db->collection($target_b);
					for my $b_id ( @{ $links->{ $target_b } } )
					{
						my $end = $delay->begin(0);
						_link($col_b, $b_id, $target_a, $a_id , sub {
							$end->([@_]);	
						});
					}
				}

			}
		
		},
	);

	$delay->on(finish => sub {
		my ($delay, @args) = @_;
		warn "allLink: on finish";
		warn Dumper \@args;

		
		if (ref $args[0] eq 'ARRAY')
		{
			my $op = false;
			my @ops = map { $_->[0] } @args;
			$op ||= $_ for @ops;
			my @errs = map { $_->[1] } @args;
			$cb->($op, \@errs);
		}
		else
		{
			$cb->(@args);
		}
	});
}

#TODO Performance Optimization & error process
sub unlinkAll
{
	my ($target_a, $a_id, $cb) = @_;
	getAll($target_a, $a_id, sub {
		my ($links, $err) = @_;

		my $delay = Mojo::IOLoop->delay(sub {
			$cb->();	
		});

		for my $target_b ( keys %$links )
		{
			for my $b_id ( @{$links->{$target_b}})
			{
				my $end = $delay->begin;
				unlinkWith($target_a, $a_id, $target_b, $b_id , sub {
					$end->();	
				});
			}
		}
	});
}


1;
